/**
 * 
 */
package com.gr.staffpm.pages.schedule;

import java.util.ArrayList;
import java.util.List;

import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.joda.time.DateTime;
import org.joda.time.Period;
import org.joda.time.PeriodType;

import com.gr.staffpm.model.StringModel;
import com.gr.staffpm.pages.schedule.model.EmptyEvent;
import com.gr.staffpm.pages.schedule.model.Event;
import com.gr.staffpm.utils.ListSortUtils;


/**
 * @author Graham Rhodes 19 Mar 2011 20:31:06
 */
public class WeekRow extends Panel {

    private static final long serialVersionUID = 1L;
    private final RepeatingView monthBGDays = new RepeatingView("monthBGDays");
    private final RepeatingView dayTitles = new RepeatingView("dayTitles");
    private final RepeatingView events = new RepeatingView("events");
    private final List<EventPanel> eventPanels = new ArrayList<EventPanel>();
    private int panelLengths[] = new int[eventPanels.size()];
    private final WebMarkupContainer weekRow = new WebMarkupContainer("weekRow");
    private final List<Event> eventsInWeek;
    private int maxEventsPerDay = 4;
    private Label weekNumber;
    private final DateTime weekStartDate;
    private final DateTime endOfWeek;
    private final boolean onlyAllDay;

    //private final Logger log = LoggerFactory.getLogger(getClass());

    public WeekRow(String id, final boolean onlyAllDay, final DateTime weekStartDate, final DateTime monthDate, List<Event> eventsInWeek) {
        super(id);
        //weekRow.add(new MaxDayEventsBehaviour(this));
        this.weekStartDate = weekStartDate;
        this.eventsInWeek = eventsInWeek;
        this.endOfWeek = weekStartDate.plusDays(7);
        this.onlyAllDay = onlyAllDay;

        DateTime currDate = weekStartDate;
        DateTime startOfCalDay = new DateTime().withDayOfMonth(monthDate.dayOfMonth().getMinimumValue());
        if (startOfCalDay.getDayOfWeek() != 7)
            startOfCalDay = startOfCalDay.minusDays(startOfCalDay.getDayOfWeek());

        final boolean isFirstWeekOfCal = (weekStartDate.getDayOfMonth() == startOfCalDay.getDayOfMonth() && weekStartDate.getMonthOfYear() == startOfCalDay.getMonthOfYear());

        weekRow.add(weekNumber = new Label("weekNum", new StringModel(String.valueOf(weekStartDate.getWeekOfWeekyear()))));

        for (int i = 0; i < 7; i++) {
            final DateTime wsDate = currDate;
            final boolean prevMonthDay = (wsDate.getMonthOfYear() != monthDate.getMonthOfYear() || wsDate.getYear() != monthDate.getYear());
            monthBGDays.add(new WebMarkupContainer(String.valueOf(i)) {

                private static final long serialVersionUID = 1L;

                @Override
                protected void onComponentTag(ComponentTag tag) {
                    String cls = "ext-cal-day " + (wsDate.equals(weekStartDate) ? "ext-cal-day-first " : " ") + (!onlyAllDay && prevMonthDay ? "ext-cal-day-prev " : " ");
                    cls += isToday(wsDate) ? "ext-cal-day-today" : "";
                    tag.getAttributes().put("class", cls);
                }

            });
            String dayTitle = wsDate.toString("d");
            if ((onlyAllDay && i == 0) || (wsDate.equals(weekStartDate) && prevMonthDay))
                dayTitle = wsDate.toString("MMM d, yyyy");
            else if (onlyAllDay || (wsDate.getDayOfMonth() == 1 && wsDate.getMonthOfYear() == monthDate.getMonthOfYear()))
                dayTitle = wsDate.toString("MMM d");

            dayTitles.add(new Label(String.valueOf(i), dayTitle) {

                private static final long serialVersionUID = 1L;

                @Override
                protected void onComponentTag(ComponentTag tag) {
                    String cls = "ext-cal-dtitle " + (isFirstWeekOfCal ? "ext-cal-dtitle-first " : " ") + (prevMonthDay ? "ext-cal-dtitle-prev" : "");
                    tag.getAttributes().put("class", cls);
                }
            });
            currDate = currDate.plusDays(1);
        }

        List<List<Event>> sortedEventLists = ListSortUtils.sortEventsByDayAndLength(eventsInWeek, weekStartDate, onlyAllDay, false);
        updateCalendarWithEvents(null, sortedEventLists);

        weekRow.add(monthBGDays);
        weekRow.add(dayTitles);
        weekRow.add(events);
        add(weekRow);
    }

    public void setMaxEvents(int maxEvents, AjaxRequestTarget target) {
        if (maxEventsPerDay != maxEvents) {
            maxEventsPerDay = 4;
            List<List<Event>> sortedEventLists = ListSortUtils.sortEventsByDayAndLength(eventsInWeek, weekStartDate, onlyAllDay, false);
            updateCalendarWithEvents(target, sortedEventLists);
        }
    }

    public void updateCalendarWithEvents(AjaxRequestTarget target, List<List<Event>> dayEventLists) {

        eventPanels.removeAll(eventPanels);
        panelLengths = new int[maxEventsPerDay];
        for (int i = 0; i < maxEventsPerDay; i++) {
            eventPanels.add(new EventPanel(weekStartDate.toString("ddMMyyyy") + String.valueOf(i)));
            panelLengths[i] = 0;
        }

        int dayIndex = 0;
        for (List<Event> dayEvents : dayEventLists) {
            //log.info("Day: " + dayIndex);
            int eventIndex = 0, panelIndex = 0;
            for (; panelIndex < eventPanels.size();) {
                EventPanel panel = eventPanels.get(panelIndex);
                //log.info("Panel: " + panel.getId());
                if (dayEvents.isEmpty()) {
                    //log.info("Day is empty");
                    if (panelLengths[panelIndex] >= 7) {
                        //log.info("panel full, next panel.");
                        panelIndex++;
                        panel = eventPanels.get(panelIndex);
                    }
                    if (panelLengths[panelIndex] < 7) {
                        //log.info("Day contains no events add empty space for entire day in all event panels");
                        panel.add(new EmptyEvent(), 0, eventPanels.size(), this, panelLengths[panelIndex]);
                        for (int i = 0; i < panelLengths.length; i++)
                            panelLengths[i] += 1;
                    }
                    break;
                }
                if (eventIndex >= dayEvents.size()) {
                    //log.info("No more events on day");
                    if (panelLengths[panelIndex] < 7) {
                        //log.info("Day contains no more events add empty space for the rest of the day in all event panels");
                        panel.add(new EmptyEvent(), 0, eventPanels.size() - eventIndex, this, panelLengths[panelIndex]);
                        for (int i = 0; i < panelLengths.length; i++)
                            panelLengths[i] += 1;
                    }
                    break;
                }
                Event event = dayEvents.get(eventIndex);

                if (!event.isAddedToEventPanel()) {
                    if (panelLengths[panelIndex] >= 7) {
                        //log.info("panel full, next panel.");
                        panelIndex++;
                        panel = eventPanels.get(panelIndex);
                    }
                    //log.info("add event \"" + event.getTitle() + "\" to panel: " + panel.getId());
                    panel.add(event, this, panelLengths[panelIndex]);
                    event.setAddedToEventPanel(true);
                    panelLengths[panelIndex] += event.getDaysSpannedTo(endOfWeek);
                    //log.info("fill extra space for panel: " + panel.getId());
                    fillExtraSpace(event, dayIndex, panel, panelIndex, dayEventLists);
                }
                //log.info("next panel");
                eventIndex++;
                panelIndex++;
            }
            dayIndex++;
        }
        for (EventPanel panel : eventPanels) {
            if (panel.hasChildren()) {
                events.addOrReplace(panel);
            }
        }
        if (target != null)
            target.addComponent(weekRow);

    }

    private void fillExtraSpace(Event event, int dayIndex, EventPanel panel, int panelIndex, List<List<Event>> dayEventLists) {
        //log.info("Event \"" + event.getTitle() + "\": ends on " + event.getEnd().toString("dd MMM yy"));
        if (event.endsBeforeEndOfWeek(endOfWeek)) {
            //log.info("event ends before the end of the week(" + endOfWeek.toString("dd MMM yy") + ").");
            //find largest event starting after event.end
            Event eventStartingAfter = getEventsStartingAfter(dayEventLists, dayIndex, event.getEnd());
            if (eventStartingAfter != null && !eventStartingAfter.isAddedToEventPanel()) {
                if (eventStartingAfter != null) {
                    //find padding needed
                    //log.info("Period between: " + event.getEnd().withHourOfDay(event.getEnd().hourOfDay().getMaximumValue()).toString() + " and " + eventStartingAfter.getStart().toString());
                    Period p = new Period(event.getEnd().withHourOfDay(event.getEnd().hourOfDay().getMaximumValue()), eventStartingAfter.getStart(), PeriodType.days());
                    //log.info("Days between event end and start of filler event: " + p.getDays());
                    //log.info("Panel length: " + panelLengths[panelIndex]);
                    //&& ((panelLengths[panelIndex] + p.getDays()) <= 7)
                    if (p.getDays() > 0) {
                        //log.info("add empty space");
                        int colspan = Math.min(p.getDays(), (7 - panelLengths[panelIndex]));
                        if ((7 - panelLengths[panelIndex]) < 0)
                            colspan = p.getDays();
                        panel.add(new EmptyEvent(), colspan, 0, this, panelLengths[panelIndex]);
                        panelLengths[panelIndex] += colspan;
                    }
                    //log.info("Add event \"" + eventStartingAfter.getTitle() + "\"");
                    panel.add(eventStartingAfter, this, panelLengths[panelIndex]);
                    panelLengths[panelIndex] += eventStartingAfter.getDaysSpannedTo(endOfWeek);
                    eventStartingAfter.setAddedToEventPanel(true);
                    //log.info("fill extra space for panel: " + panel.getId());
                    fillExtraSpace(eventStartingAfter, dayIndex, panel, panelIndex, dayEventLists);
                }
            } else {
                //log.info("Period between: " + event.getEnd().withHourOfDay(event.getEnd().hourOfDay().getMaximumValue()).toString() + " and " + endOfWeek.toString());
                Period p = new Period(event.getEnd().withHourOfDay(event.getEnd().hourOfDay().getMaximumValue()), endOfWeek, PeriodType.days());
                //log.info("Days between event end and end of week: " + p.getDays());
                //log.info("Panel length: " + panelLengths[panelIndex]);
                if (p.getDays() > 0 && ((panelLengths[panelIndex] + p.getDays()) <= 7)) {
                    //log.info("Add empty space");
                    int colspan = Math.min(p.getDays() - 1, (7 - panelLengths[panelIndex]));
                    if ((7 - panelLengths[panelIndex]) < 0)
                        colspan = p.getDays() - 1;
                    panel.add(new EmptyEvent(), colspan, 0, this, panelLengths[panelIndex]);
                    panelLengths[panelIndex] += colspan;
                }
            }
        }
    }

    private synchronized Event getEventsStartingAfter(List<List<Event>> dayEventLists, int beginIndex, DateTime end) {
        List<Event> eventsAfter = new ArrayList<Event>();
        for (; beginIndex < dayEventLists.size(); beginIndex++) {
            List<Event> events = dayEventLists.get(beginIndex);
            for (Event event : events) {
                if (event.startsOnOrAfter(end.plusDays(1))) {
                    eventsAfter.add(event);
                }
            }
        }
        if (eventsAfter.isEmpty())
            return null;
        ListSortUtils.sortEventsByDate(eventsAfter);
        Event fillEvent = null;
        for (int i = 0; i < eventsAfter.size(); i++) {
            if (!eventsAfter.get(i).isAddedToEventPanel()) {
                fillEvent = eventsAfter.get(i);
                //log.info("fill with event \"" + fillEvent.getTitle() + "\"");
                break;
            }
        }
        return fillEvent;
    }

    protected boolean isToday(DateTime dt) {
        DateTime now = new DateTime();
        return (dt.getDayOfMonth() == now.getDayOfMonth() && dt.getMonthOfYear() == now.getMonthOfYear() && dt.getYear() == now.getYear());
    }

    public DateTime getWeekStart() {
        return weekStartDate;
    }

    public DateTime getWeekEnd() {
        return endOfWeek;
    }

    public int getPanelLength(int panelIndex) {
        return panelLengths[panelIndex];
    }

    public void setWeekNumberVisible(boolean visible) {
        weekNumber.setVisible(visible);
    }

}
